package com.unirio.pm.passwordchecker;

public class PasswordChecker02 {

    private int upperCaseCharactersCounter = 0;
    private int lowerCaseCharactersCounter = 0;
    private int numericCharactersCounter = 0;
    private int symbolCharactersCounter = 0;
    private int midOccurrenceDigitSymbolCounter = 0;
    private int uniqueCharsCounter = 0;
    private int repeatedCharsCounter = 0;
    private int upperCaseConsecutiveCharactersCounter = 0;
    private int lowerCaseConsecutiveCharactersCounter = 0;
    private int consecutiveNumbersCounter = 0;
    private int consecutiveSymbolCounter = 0;
    private int consecutiveCharactersCounter = 0;
    private double incrementerRepeatedChars = 0.0;

    /**
     *
     * @param password
     */
    public void checkPassword(String password) {
        int passwordScore = calcPasswordScore(password);
        String passwordComplexity = evaluatePassword(passwordScore);
        System.out.println("Strength score is: " + passwordScore + "%");
        System.out.println("Strength verdict: " + passwordComplexity);
    }

    /**
     *
     * @param password
     * @return
     */
    public int calcPasswordScore(String password) {
        int passwordScore = 0;
        int passwordLength = 0;

        int sequencialAlphabeticalCharactersCounter = 0;
        int sequencialNumericCharactersCounter = 0;
        int sequencialSymbolCharactersCounter = 0;
        int sequencialCharactersCounter = 0;

        String alphabeticCharacters = "abcdefghijlmnopqrstuvwxyz";
        String numericCharacters = "01234567890";
        String symbolCharacters = ")!@#$%^&*()";

        int minimumPasswordLength = 8;

        if (isValid(password)) {
            passwordScore = getScoreByLength(password);
            passwordLength = password.length();
            char[] passwordNoBlankSpaces = removeBlankSpaces(password).toCharArray();

            calcCounters(passwordNoBlankSpaces);

            Sequence alphaSequence = checkForPattern(22, password, alphabeticCharacters, sequencialAlphabeticalCharactersCounter, sequencialCharactersCounter);
            sequencialAlphabeticalCharactersCounter = alphaSequence.getSequencialCharactersCounter();
            sequencialCharactersCounter = alphaSequence.getSequencialCounter();

            Sequence numericSequence = checkForPattern(8, password, numericCharacters, sequencialNumericCharactersCounter, sequencialCharactersCounter);
            sequencialNumericCharactersCounter = numericSequence.getSequencialCharactersCounter();
            sequencialCharactersCounter = numericSequence.getSequencialCounter();

            Sequence symbolSequence = checkForPattern(8, password, symbolCharacters, sequencialSymbolCharactersCounter, sequencialCharactersCounter);
            sequencialSymbolCharactersCounter = symbolSequence.getSequencialCharactersCounter();
            sequencialCharactersCounter = symbolSequence.getSequencialCounter();

            passwordScore += calcUpperCaseCharactersScore(upperCaseCharactersCounter, passwordLength);
            passwordScore += calcLowerCaseCharactersScore(lowerCaseCharactersCounter, passwordLength);
            passwordScore += calcNumericCharactersScore(numericCharactersCounter, passwordLength);
            passwordScore += calcSymbolCharactersScore(symbolCharactersCounter);
            passwordScore += calcMidCharScore(midOccurrenceDigitSymbolCounter);

            passwordScore -= (isOnlyAlphabetic() ? passwordLength : 0);

            passwordScore -= (isOnlyNumeric() ? passwordLength : 0);

            passwordScore -= calcRepeatedCharsScore(repeatedCharsCounter, incrementerRepeatedChars);
            passwordScore -= calcUpperCaseConsecutivesScore(upperCaseConsecutiveCharactersCounter);
            passwordScore -= calcLowerCaseConsecutivesScore(lowerCaseConsecutiveCharactersCounter);
            passwordScore -= calcConsecutiveNumbersScore(consecutiveNumbersCounter);
            passwordScore -= calcSequencialCharactersScore(sequencialAlphabeticalCharactersCounter);
            passwordScore -= calcSequencialNumericsScore(sequencialNumericCharactersCounter);
            passwordScore -= calcSequencialSymbolsScore(sequencialSymbolCharactersCounter);

            int requiredCharsCounter = calcRequiredCharsCounter(passwordLength, minimumPasswordLength);
            int minimumRequiredCharacters = getMinimumRequiredChars(passwordLength, minimumPasswordLength);
            passwordScore += calcByRequiredChars(requiredCharsCounter, minimumRequiredCharacters);

            passwordScore = normalize(passwordScore);
        }
        return passwordScore;
    }

    /**
     * Calcula o contador de caracteres obrigatorios.
     *
     * @param passwordLength
     * @param minimumPasswordLength
     * @return
     */
    public int calcRequiredCharsCounter(int passwordLength, int minimumPasswordLength) {
        int requiredCharsCounter = 0;
        int[] counters = {passwordLength, upperCaseCharactersCounter, lowerCaseCharactersCounter, numericCharactersCounter, symbolCharactersCounter};
        String[] countersName = {"nLength", "nAlphaUC", "nAlphaLC", "nNumber", "nSymbol"};
        int countersArrayLength = counters.length;
        int minimumValue;
        for (int c = 0; c < countersArrayLength; c++) {

            if (countersName[c].equals("nLength")) {
                minimumValue = minimumPasswordLength - 1;
            } else {
                minimumValue = 0;
            }
            if (counters[c] == (minimumValue + 1)) {
                requiredCharsCounter++;
            } else if (counters[c] > (minimumValue + 1)) {
                requiredCharsCounter++;
            }
        }
        return requiredCharsCounter;
    }

    /**
     * Verifica se a senha nao e nula e nao vazia.
     *
     * @param password
     * @return
     */
    public boolean isValid(String password) {
        return password != null && !password.equals("");
    }

    /**
     * Calcula todos os contadores.
     *
     * @param passwordNoBlankSpaces
     */
    public void calcCounters(char[] passwordNoBlankSpaces) {
        int upperCaseTempCharacter = -1, lowerCaseTempCharacter = -1;
        int tempNumber = -1, symbolTempCharacter = -1;
        int passwordNoBlankSpacesLength = passwordNoBlankSpaces.length;
        for (int i = 0; i < passwordNoBlankSpacesLength; i++) {
            char currentLetter = passwordNoBlankSpaces[i];

            if (isUpperCaseLetter(currentLetter)) {
                if (upperCaseTempCharacter != -1) {
                    if ((upperCaseTempCharacter + 1) == i) {
                        upperCaseConsecutiveCharactersCounter++;
                        consecutiveCharactersCounter++;
                    }
                }
                upperCaseTempCharacter = i;
                upperCaseCharactersCounter++;
            } else if (isLowerCaseLetter(currentLetter)) {
                if (lowerCaseTempCharacter != -1) {
                    if ((lowerCaseTempCharacter + 1) == i) {
                        lowerCaseConsecutiveCharactersCounter++;
                        consecutiveCharactersCounter++;
                    }
                }
                lowerCaseTempCharacter = i;
                lowerCaseCharactersCounter++;
            } else if (isDigit(currentLetter)) {
                if (i > 0 && i < (passwordNoBlankSpacesLength - 1)) {
                    midOccurrenceDigitSymbolCounter++;
                }
                if (tempNumber != -1) {
                    if ((tempNumber + 1) == i) {
                        consecutiveNumbersCounter++;
                        consecutiveCharactersCounter++;
                    }
                }
                tempNumber = i;
                numericCharactersCounter++;
            } else if (isSymbol(currentLetter)) {
                if (i > 0 && i < (passwordNoBlankSpacesLength - 1)) {
                    midOccurrenceDigitSymbolCounter++;
                }
                if (symbolTempCharacter != -1) {
                    if ((symbolTempCharacter + 1) == i) {
                        consecutiveSymbolCounter++;
                        consecutiveCharactersCounter++;
                    }
                }
                symbolTempCharacter = i;
                symbolCharactersCounter++;
            }

            boolean isCharRepeated = false;
            for (int b = 0; b < passwordNoBlankSpacesLength; b++) {
                if (currentLetter == passwordNoBlankSpaces[b] && i != b) {
                    isCharRepeated = true;
                    incrementerRepeatedChars += Math.abs((double) passwordNoBlankSpacesLength / (b - i));
                }
            }
            if (isCharRepeated) {
                repeatedCharsCounter++;
                uniqueCharsCounter = passwordNoBlankSpacesLength - repeatedCharsCounter;
                incrementerRepeatedChars = (uniqueCharsCounter != 0) ? (int) Math.ceil(incrementerRepeatedChars / uniqueCharsCounter) : (int) Math.ceil(incrementerRepeatedChars);
            }
        }
    }

    /**
     *
     * @param requirements
     * @param minimumRequiredCharacters
     * @return
     */
    public int calcByRequiredChars(int requirements, int minimumRequiredCharacters) {
        return (requirements > minimumRequiredCharacters)
                ? (requirements * 2) : 0;
    }

    /**
     * Obtem o numero de caracteres minimo.
     *
     * @param passwordLength
     * @param minimumPasswordLength
     * @return
     */
    public int getMinimumRequiredChars(int passwordLength, int minimumPasswordLength) {
        int minimumRequiredCharacters;
        if (passwordLength >= minimumPasswordLength) {
            minimumRequiredCharacters = 3;
        } else {
            minimumRequiredCharacters = 4;
        }
        return minimumRequiredCharacters;
    }

    /**
     * Verifica a existencia do pattern na senha e incrementa os contadores caso
     * encontre.
     *
     * @param limit
     * @param password
     * @param pattern
     * @param sequencialCounter
     * @param sequencialCharactersCounter
     *
     * @return
     */
    public Sequence checkForPattern(int limit, String password, String pattern, int sequencialAlphabeticalCharactersCounter, int sequencialCharactersCounter) {
        Sequence sequence = new Sequence(sequencialCharactersCounter, sequencialAlphabeticalCharactersCounter);
        for (int s = 0; s < limit; s++) {
            if (hasPattern(password, pattern, s)) {
                sequence.incrementCounters();
            }
        }
        return sequence;
    }

    /**
     * Verifica a existencia do pattern na senha.
     *
     * @param password
     * @param pattern
     * @param position
     * @return
     */
    public boolean hasPattern(String password, String pattern, int position) {
        String interval = pattern.substring(position, position + 3);
        String reversedInterval = new StringBuffer(interval).reverse().toString();
        return containsInterval(password, interval) || containsInterval(password, reversedInterval);
    }

    /**
     * Calcula o contador de simbolos sequenciais.
     *
     * @param sequencialSymbolCharactersCounter
     * @return
     */
    public int calcSequencialSymbolsScore(int sequencialSymbolCharactersCounter) {
        int sequencialSymbolCharactersMultiplier = 3;
        return (sequencialSymbolCharactersCounter > 0)
                ? (sequencialSymbolCharactersCounter * sequencialSymbolCharactersMultiplier) : 0;
    }

    /**
     * Calcula o contador de numeros sequenciais.
     *
     * @param sequencialNumericCharactersCounter
     * @return
     */
    public int calcSequencialNumericsScore(int sequencialNumericCharactersCounter) {
        int sequencialNumericCharactersMultiplier = 3;
        return (sequencialNumericCharactersCounter > 0)
                ? (sequencialNumericCharactersCounter * sequencialNumericCharactersMultiplier) : 0;
    }

    /**
     * Calcula o contador de letras sequenciais.
     *
     * @param sequencialAlphabeticalCharactersCounter
     * @return
     */
    public int calcSequencialCharactersScore(int sequencialAlphabeticalCharactersCounter) {
        int sequencialAlphabeticalCharactersMultiplier = 3;
        return (sequencialAlphabeticalCharactersCounter > 0)
                ? (sequencialAlphabeticalCharactersCounter * sequencialAlphabeticalCharactersMultiplier) : 0;
    }

    /**
     * Calcula o contador de numeros consecutivos.
     *
     * @param consecutiveNumbersCounter
     * @return
     */
    public int calcConsecutiveNumbersScore(int consecutiveNumbersCounter) {
        int consecutiveNumbersMultiplier = 2;
        return (consecutiveNumbersCounter > 0)
                ? (consecutiveNumbersCounter * consecutiveNumbersMultiplier) : 0;
    }

    /**
     * Calcula o contador de letras minusculas consecutivas.
     *
     * @param lowerCaseConsecutiveCharactersCounter
     * @return
     */
    public int calcLowerCaseConsecutivesScore(int lowerCaseConsecutiveCharactersCounter) {
        int lowerCaseConsecutiveCharactersMultiplier = 2;
        return (lowerCaseConsecutiveCharactersCounter > 0)
                ? (lowerCaseConsecutiveCharactersCounter * lowerCaseConsecutiveCharactersMultiplier) : 0;
    }

    /**
     * Calcula o contador de letras maiusculas consecutivas.
     *
     * @param upperCaseConsecutiveCharactersCounter
     * @return
     */
    public int calcUpperCaseConsecutivesScore(int upperCaseConsecutiveCharactersCounter) {
        int upperCaseConsecutiveCharactersMultiplier = 2;
        return (upperCaseConsecutiveCharactersCounter > 0)
                ? (upperCaseConsecutiveCharactersCounter * upperCaseConsecutiveCharactersMultiplier) : 0;
    }

    /**
     * Calcula o contador de letras repetidas.
     *
     * @param repeatedCharsCounter
     * @param incrementerRepeatedChars
     * @return
     */
    public int calcRepeatedCharsScore(int repeatedCharsCounter, double incrementerRepeatedChars) {
        return (repeatedCharsCounter > 0)
                ? ((int) incrementerRepeatedChars) : 0;
    }

    /**
     * Verifica se somente os contadores de letras sao maiores que zero.
     *
     * @return
     */
    public boolean isOnlyAlphabetic() {
        return ((lowerCaseCharactersCounter > 0 || upperCaseCharactersCounter > 0)
                && symbolCharactersCounter == 0 && numericCharactersCounter == 0);
    }

    /**
     * Calcula o contador de ocorrencias de simbolo/digito no meio da senha
     *
     * @param midOccurrenceDigitSymbolCounter
     * @return
     */
    public int calcMidCharScore(int midOccurrenceDigitSymbolCounter) {
        int midDigitSymbolMultiplier = 2;
        return (midOccurrenceDigitSymbolCounter > 0)
                ? (midOccurrenceDigitSymbolCounter * midDigitSymbolMultiplier) : 0;
    }

    /**
     * Calcula o contador de simbolos.
     *
     * @param symbolCharactersCounter
     * @return
     */
    public int calcSymbolCharactersScore(int symbolCharactersCounter) {
        int symbolMultiplier = 6;
        return (symbolCharactersCounter > 0)
                ? (symbolCharactersCounter * symbolMultiplier) : 0;
    }

    /**
     * Calcula o contador de numeros.
     *
     * @param numericCharactersCounter
     * @param passwordLength
     * @return
     */
    public int calcNumericCharactersScore(int numericCharactersCounter, int passwordLength) {
        int numericMultiplier = 4;
        return (numericCharactersCounter > 0 && numericCharactersCounter < passwordLength)
                ? (numericCharactersCounter * numericMultiplier) : 0;
    }

    /**
     * Calcula o contador de letras minusculas.
     *
     * @param lowerCaseCharactersCounter
     * @param passwordLength
     * @return
     */
    public int calcLowerCaseCharactersScore(int lowerCaseCharactersCounter, int passwordLength) {
        return (lowerCaseCharactersCounter > 0 && lowerCaseCharactersCounter < passwordLength)
                ? ((passwordLength - lowerCaseCharactersCounter) * 2) : 0;
    }

    /**
     * Calcula o contador de letras maiusculas.
     *
     * @param upperCaseCharactersCounter
     * @param passwordLength
     * @return
     */
    public int calcUpperCaseCharactersScore(int upperCaseCharactersCounter, int passwordLength) {
        return (upperCaseCharactersCounter > 0 && upperCaseCharactersCounter < passwordLength)
                ? ((passwordLength - upperCaseCharactersCounter) * 2) : 0;
    }

    /**
     * Atribui valores limites a pontuação, sendo o maior valor 100 e o menor 0.
     *
     * @param passwordScore
     * @return
     */
    public int normalize(int passwordScore) {
        if (passwordScore > 100) {
            passwordScore = 100;
        } else if (passwordScore < 0) {
            passwordScore = 0;
        }
        return passwordScore;
    }

    /**
     * Verifica a complexidade da senha de acordo com a pontuação
     *
     * @param passwordScore
     * @return
     */
    public String evaluatePassword(int passwordScore) {
        if (passwordScore >= 0 && passwordScore < 20) {
            return "Very Weak";
        } else if (passwordScore >= 20 && passwordScore < 40) {
            return "Weak";
        } else if (passwordScore >= 40 && passwordScore < 60) {
            return "Medium";
        } else if (passwordScore >= 60 && passwordScore < 80) {
            return "Strong";
        } else if (passwordScore >= 80 && passwordScore <= 100) {
            return "Very Strong";
        }
        return "Too Short";
    }

    /**
     * Verifica se o parametro passado é um simbolo.
     *
     * @param currentLetter
     * @return
     */
    public boolean isSymbol(char currentLetter) {
        return !isDigit(currentLetter) && !Character.isLetter(currentLetter) && currentLetter != '_';
    }

    /**
     * Verifica se o parametro passado é um digito
     *
     * @param currentLetter
     * @return
     */
    public boolean isDigit(char currentLetter) {
        return Character.isDigit(currentLetter);
    }

    /**
     * Verifica se o parametro passado é letra e minuscula.
     *
     * @param letter a letra a ser verificada
     * @return true se for letra minuscula, false se nao for
     */
    public boolean isLowerCaseLetter(char letter) {
        return Character.isLetter(letter) && Character.isLowerCase(letter);
    }

    /**
     * Verifica se o parametro passado é letra e maiuscula.
     *
     * @param letter a letra a ser verificada
     * @return true se for letra maiuscula, false se nao for
     */
    public boolean isUpperCaseLetter(char letter) {
        return Character.isLetter(letter) && Character.isUpperCase(letter);
    }

    /**
     * Verifica a existencia do intervalo na palavra
     *
     * @param password a senha
     * @param interval o intervalo a ser procurado
     * @return true caso contenha, false caso nao seja encontrado o intervalo
     */
    public boolean containsInterval(String password, String interval) {
        return password.toLowerCase().contains(interval);
    }

    /**
     * Remove os espaços em branco
     *
     * @param password String a ser modificada
     * @return a String sem os espacos em branco
     */
    public String removeBlankSpaces(String password) {
        return password.replaceAll("\\s+", "");
    }

    /**
     * Verifica se somente o contador de numericos é maior que zero.
     *
     * @return
     */
    public boolean isOnlyNumeric() {
        return (lowerCaseCharactersCounter == 0 && upperCaseCharactersCounter == 0 && symbolCharactersCounter == 0 && numericCharactersCounter > 0);
    }

    /**
     * Calcula a pontuação de acordo com o tamanho da senha.
     *
     * @param password
     * @return
     */
    public int getScoreByLength(String password) {
        int lengthMultiplier = 4;
        return password.length() * lengthMultiplier;
    }

    public int getUpperCaseCharactersCounter() {
        return upperCaseCharactersCounter;
    }

    public void setUpperCaseCharactersCounter(int upperCaseCharactersCounter) {
        this.upperCaseCharactersCounter = upperCaseCharactersCounter;
    }

    public int getLowerCaseCharactersCounter() {
        return lowerCaseCharactersCounter;
    }

    public void setLowerCaseCharactersCounter(int lowerCaseCharactersCounter) {
        this.lowerCaseCharactersCounter = lowerCaseCharactersCounter;
    }

    public int getNumericCharactersCounter() {
        return numericCharactersCounter;
    }

    public void setNumericCharactersCounter(int numericCharactersCounter) {
        this.numericCharactersCounter = numericCharactersCounter;
    }

    public int getSymbolCharactersCounter() {
        return symbolCharactersCounter;
    }

    public void setSymbolCharactersCounter(int symbolCharactersCounter) {
        this.symbolCharactersCounter = symbolCharactersCounter;
    }

    public int getMidOccurrenceDigitSymbolCounter() {
        return midOccurrenceDigitSymbolCounter;
    }

    public void setMidOccurrenceDigitSymbolCounter(int midOccurrenceDigitSymbolCounter) {
        this.midOccurrenceDigitSymbolCounter = midOccurrenceDigitSymbolCounter;
    }

    public int getUniqueCharsCounter() {
        return uniqueCharsCounter;
    }

    public void setUniqueCharsCounter(int uniqueCharsCounter) {
        this.uniqueCharsCounter = uniqueCharsCounter;
    }

    public int getRepeatedCharsCounter() {
        return repeatedCharsCounter;
    }

    public void setRepeatedCharsCounter(int repeatedCharsCounter) {
        this.repeatedCharsCounter = repeatedCharsCounter;
    }

    public int getUpperCaseConsecutiveCharactersCounter() {
        return upperCaseConsecutiveCharactersCounter;
    }

    public void setUpperCaseConsecutiveCharactersCounter(int upperCaseConsecutiveCharactersCounter) {
        this.upperCaseConsecutiveCharactersCounter = upperCaseConsecutiveCharactersCounter;
    }

    public int getLowerCaseConsecutiveCharactersCounter() {
        return lowerCaseConsecutiveCharactersCounter;
    }

    public void setLowerCaseConsecutiveCharactersCounter(int lowerCaseConsecutiveCharactersCounter) {
        this.lowerCaseConsecutiveCharactersCounter = lowerCaseConsecutiveCharactersCounter;
    }

    public int getConsecutiveNumbersCounter() {
        return consecutiveNumbersCounter;
    }

    public void setConsecutiveNumbersCounter(int consecutiveNumbersCounter) {
        this.consecutiveNumbersCounter = consecutiveNumbersCounter;
    }

    public int getConsecutiveSymbolCounter() {
        return consecutiveSymbolCounter;
    }

    public void setConsecutiveSymbolCounter(int consecutiveSymbolCounter) {
        this.consecutiveSymbolCounter = consecutiveSymbolCounter;
    }

    public int getConsecutiveCharactersCounter() {
        return consecutiveCharactersCounter;
    }

    public void setConsecutiveCharactersCounter(int consecutiveCharactersCounter) {
        this.consecutiveCharactersCounter = consecutiveCharactersCounter;
    }

    public double getIncrementerRepeatedChars() {
        return incrementerRepeatedChars;
    }

    public void setIncrementerRepeatedChars(double incrementerRepeatedChars) {
        this.incrementerRepeatedChars = incrementerRepeatedChars;
    }

}
